# +----------------------------------------------------------------------
# | ChatWork智能聊天办公系统
# +----------------------------------------------------------------------
# | 软件声明: 本系统并非自由软件,未经授权任何形式的商业使用均属非法。
# | 版权保护: 任何企业和个人不允许对程序代码以任何形式任何目的复制/分发。
# | 授权要求: 如有商业使用需求,请务必先与版权所有者取得联系并获得正式授权。
# +----------------------------------------------------------------------
# | Author: ChatWork Team <2474369941@qq.com>
# +----------------------------------------------------------------------
import json
from decimal import Decimal
from alipay import AliPay
from alipay.utils import AliPayConfig
from kernels.utils import RequestUtil
from exception import AppException
from common.enums.pay import PayEnum
from common.enums.client import ClientEnum
from common.models.dev import DevPayConfigModel


class AlipayService:
    """
    GitHub: https://github.com/fzlee/alipay
    文档地址: https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
    """

    @classmethod
    async def unify_order(cls, terminal: int, attach: str, order: dict, qr: bool = False):
        """
        预支付下单 (根据设备自动选择方式)

        Args:
            terminal: int (设备: 3=H5,4=电脑,5=安卓,6=苹果)
            attach: str (订单类别)
            order: dict (订单参数)
            qr: bool (是否使用二维码方式: 仅PC下生效)

        Returns:
            dict|str

        Authro:
            zero
        """
        order_params = {
            "out_trade_no": order.get("out_trade_no", ""),
            "description": order.get("description", ""),
            "order_amount": order.get("order_amount", 0),
            "redirect_url": order.get("redirect_url", "")
        }

        if terminal == ClientEnum.PC:
            if qr:
                return await cls.pre_create(attach, order_params)
            else:
                return await cls.page_pay(attach, order_params)
        elif terminal == ClientEnum.H5:
            return await cls.wap_pay(attach, order_params)
        elif terminal == ClientEnum.IOS or terminal == ClientEnum.ANDROID:
            return await cls.app_pay(attach, order_params)
        else:
            return await cls.mnp_pay(attach, order_params)

    @classmethod
    async def alipay(cls):
        """ 实例化支付宝对象 """
        try:
            config = await cls.options()
            return AliPay(
                appid=config.get("app_id"),
                app_notify_url=config.get("notify_url"),
                app_private_key_string=config.get("private_key"),
                alipay_public_key_string=config.get("public_key"),
                sign_type="RSA2",  # RSA或RSA2
                debug=False,       # 默认为False
                verbose=False,     # 输出调试数据
                config=AliPayConfig(timeout=15)
            )
        except Exception as e:
            raise AppException(str(e))

    @classmethod
    async def options(cls):
        """ 获取支付参数 """
        config = await DevPayConfigModel.filter(channel=PayEnum.WAY_ALI).get()
        params = json.loads(config.params)
        app_id: str = params.get("app_id", "").strip()
        private_key: str = params.get("private_key", "").strip()
        public_key: str = params.get("public_key", "").strip()

        if not private_key:
            raise Exception("Private key cannot be empty")
        if not public_key:
            raise Exception("Public key cannot be empty")

        if not private_key.startswith("-----BEGIN PRIVATE KEY-----"):
            raise Exception("Private key error: It must start with -----BEGIN PRIVATE KEY-----")
        if not private_key.endswith("-----END PRIVATE KEY-----"):
            raise Exception("Private key error: It must end with -----END PRIVATE KEY-----")

        if not public_key.startswith("-----BEGIN PUBLIC KEY-----"):
            raise Exception("Public key error: It must start with -----BEGIN PUBLIC KEY-----")
        if not public_key.endswith("-----END PUBLIC KEY-----"):
            raise Exception("Public key error: It must end with -----END PUBLIC KEY-----")

        return {
            "app_id": app_id,
            "private_key": private_key,
            "public_key": public_key,
            "notify_url": f"{RequestUtil.domain}/{RequestUtil.module}/payment/notify_ali"
        }

    @classmethod
    async def page_pay(cls, attach: str, order_params: dict) -> str:
        """ 电脑网站支付(PC) """
        _pay = await cls.alipay()
        return_url = RequestUtil.domain + order_params.get("redirect_url", "").rstrip("/")
        order_string = _pay.api_alipay_trade_page_pay(
            out_trade_no=order_params.get("out_trade_no"),
            total_amount=str(order_params.get("order_amount")),
            subject=order_params.get("description"),
            return_url=return_url,
            passback_params=attach,
        )
        return "https://openapi.alipay.com/gateway.do?" + order_string

    @classmethod
    async def wap_pay(cls, attach: str, order_params: dict) -> str:
        """ 手机网站支付(H5) """
        _pay = await cls.alipay()
        return_url = RequestUtil.domain + order_params.get("redirect_url", "").rstrip("/")
        order_string = _pay.api_alipay_trade_wap_pay(
            out_trade_no=order_params.get("out_trade_no"),
            total_amount=str(order_params.get("order_amount")),
            subject=order_params.get("description"),
            return_url=return_url,
            passback_params=attach,
        )
        return "https://openapi.alipay.com/gateway.do?" + order_string

    @classmethod
    async def app_pay(cls, attach: str, order_params: dict):
        """ App支付 """
        _pay = await cls.alipay()
        return _pay.api_alipay_trade_app_pay(
            out_trade_no=order_params.get("out_trade_no"),
            total_amount=order_params.get("order_amount"),
            subject=order_params.get("description"),
            passback_params=attach
        )

    @classmethod
    async def mnp_pay(cls, attach: str, order_params: dict):
        """ 小程序支付 """
        _pay = await cls.alipay()
        return _pay.api_alipay_trade_create(
            out_trade_no=order_params.get("out_trade_no"),
            total_amount=order_params.get("order_amount"),
            subject=order_params.get("description"),
            buyer_id=order_params.get("buyer_id", ""),
            passback_params=attach
        )

    @classmethod
    async def pre_create(cls, attach: str, order_params: dict) -> str:
        """ 交易预创建(扫码支付)  """
        _pay = await cls.alipay()
        result = _pay.api_alipay_trade_precreate(
            out_trade_no=order_params.get("out_trade_no"),
            total_amount=str(order_params.get("order_amount")),
            subject=order_params.get("description"),
            passback_params=attach
        )

        if result is None:
            raise Exception("alipy error")
        if int(result.get("code", "0")) != 10000:
            raise Exception(result.get("msg", "alipy error"))
        return result.get("qr_code")

    @classmethod
    async def query(cls, out_trade_no: str) -> dict:
        """
        查询订单
        官方文档: https://opendocs.alipay.com/open/02ekfq
        """
        _pay = await cls.alipay()
        result = _pay.api_alipay_trade_query(
            out_trade_no=out_trade_no
        )

        if int(result.get("code", "0")) != 10000:
            raise Exception(result.get("sub_msg", "alipy error"))

        return {
            "code": result.get("code"),
            "msg": result.get("msg"),
            "buyer_logon_id": result.get("buyer_logon_id"),
            "buyer_pay_amount": result.get("buyer_pay_amount"),
            "buyer_user_id": result.get("buyer_user_id"),
            "invoice_amount": result.get("invoice_amount"),
            "out_trade_no": result.get("out_trade_no"),
            "passback_params": result.get("passback_params"),
            "point_amount": result.get("point_amount"),
            "receipt_amount": result.get("receipt_amount"),
            "send_pay_date": result.get("send_pay_date"),
            "total_amount": result.get("total_amount"),
            "trade_no": result.get("trade_no"),
            "trade_status": result.get("trade_status")
        }

    @classmethod
    async def close(cls, out_trade_no: str, transaction_id: str = None):
        """
        关闭订单
        官方文档: https://opendocs.alipay.com/open/02o6e8
        """
        _pay = await cls.alipay()
        result = _pay.api_alipay_trade_close(
            trade_no=transaction_id,
            out_trade_no=out_trade_no
        )

        if int(result.get("code", "0")) != 10000:
            raise Exception(result.get("sub_msg", "alipy error"))

        return {
            "code": result.get("code"),
            "msg": result.get("msg"),
            "trade_no": result.get("trade_no"),
            "out_trade_no": result.get("out_trade_no")
        }

    @classmethod
    async def refund(cls, transaction_id: str, out_refund_no: str, refund_amount: Decimal) -> dict:
        """
        申请退款
        官方文档: https://opendocs.alipay.com/open/02ekfs
        """
        _pay = await cls.alipay()
        result = _pay.api_alipay_trade_refund(
            trade_no=transaction_id,
            out_request_no=out_refund_no,
            refund_amount=str(refund_amount)
        )

        if int(result.get("code", "0")) != 10000:
            raise Exception(result.get("sub_msg", "alipy error"))

        return {
            "code": result.get("code"),
            "msg": result.get("msg"),
            "buyer_logon_id": result.get("buyer_logon_id"),
            "buyer_user_id": result.get("buyer_user_id"),
            "fund_change": result.get("fund_change"),
            "gmt_refund_pay": result.get("gmt_refund_pay"),
            "out_trade_no": result.get("out_trade_no"),
            "refund_fee": result.get("refund_fee"),
            "send_back_fee": result.get("send_back_fee"),
            "trade_no": result.get("trade_no")
        }

    @classmethod
    async def query_refund(cls, out_refund_no: str, transaction_id: str = None, out_trade_no: str = None):
        """
        退款查询
        官方文档: https://opendocs.alipay.com/open/02ekft
        """
        _pay = await cls.alipay()
        result = _pay.api_alipay_trade_fastpay_refund_query(
            out_request_no=out_refund_no,
            trade_no=transaction_id,
            out_trade_no=out_trade_no
        )

        if int(result.get("code", "0")) != 10000:
            raise Exception(result.get("sub_msg", "alipy error"))

        return {
            "code": result.get("code"),
            "msg": result.get("msg"),
            "out_request_no": result.get("out_request_no"),
            "out_trade_no": result.get("out_trade_no"),
            "refund_amount": result.get("refund_amount"),
            "refund_status": result.get("refund_status"),
            "total_amount": result.get("total_amount"),
            "trade_no": result.get("trade_no")
        }
